function functionCallable(value: any, { kind }: any): any {
  if (kind === "class") {
    return function (...args: any) {
      if (new.target !== undefined) {
        throw new TypeError("This function can’t be new-invoked");
      }
      return new value(...args);
    };
  }
}

@functionCallable
class Person {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
}
// @ts-ignore
// const robin = new Person("Robin");
// 上面示例中，类装饰器@functionCallable返回一个新的构造方法，里面判断new.target是否不为空，如果是的，就表示通过new命令调用，从而报错。

// customElements.define 是 Web Components 标准的一部分，用于定义自定义元素（Custom Elements）。通过它，你可以创建自己的 HTML 标签，并为其定义行为和样式。自定义元素是 Web Components 的核心技术之一，允许开发者扩展 HTML 的能力。
// console.log(customElements);

/* function customElement(name: string) {
  return <Input extends new (...args: any) => any>(
    value: Input,
    context: ClassDecoratorContext
  ) => {
    context.addInitializer(function () {
      customElements.define(name, value);
    });
  };
}

@customElement("hello-world")
class MyComponent extends HTMLElement {
  constructor() {
    super();
  }
  connectedCallback() {
    this.innerHTML = `<h1>Hello World</h1>`;
  }
} */

// 方法装饰器  方法装饰器用来装饰类的方法（method）,方法装饰器会改写类的原始方法，
/* function trace(decoratedMethod: any, context: any) {
  console.log(decoratedMethod);
  console.log(context);
}
class C {
  @trace
  toString() {
    return "C";
  }
} */

// 如果方法装饰器返回一个新的函数，就会替代所装饰的原始函数
function replaceMethod() {
  return function () {
    // @ts-ignore
    return `How are you, ${this.name}?`;
  };
}
class P1 {
  name: string;
  constructor(name: any) {
    this.name = name;
  }

  // @ts-ignore
  @replaceMethod
  hello() {
    return `Hi ${this.name}!`;
  }
}

const robin = new P1("Robin");
console.log(robin.hello());

function log(originalMethod: any, context: ClassMethodDecoratorContext) {
  const methodName = String(context.name);

  return function (this: any, ...args: any[]) {
    console.log(`LOG: Entering method '${methodName}'.`);
    // 通过执行originalMethod.call()完成了对原始方法的调用。
    const result = originalMethod.call(this, ...args);
    console.log(`LOG: Exiting method '${methodName}'.`);
    return result;
  };
}

class P2 {
  name: string;
  constructor(name: string) {
    this.name = name;
  }

  @log
  greet() {
    console.log(`Hello, my name is ${this.name}.`);
  }
}
const p2 = new P2("张三");
p2.greet();

// 利用方法装饰器，可以将类的方法变成延迟执行
// 这种通过高阶函数返回装饰器的做法，称为“工厂模式”，即可以像工厂那样生产出一个模子的装饰器。
function delay(milliseconds: number = 0) {
  return function (value: any, context: ClassMethodDecoratorContext) {
    if (context.kind === "method") {
      return function (...args: any[]) {
        setTimeout(() => {
          // @ts-ignore
          value.apply(this, args);
        }, milliseconds);
      };
    }
  };
}
class Logger {
  @delay(3000)
  log(msg: string) {
    console.log(`${msg}`);
  }
}

// let logger = new Logger();
// logger.log("Hello World");

class P3 {
  name: string;
  constructor(name: string) {
    this.name = name;

    // greet() 绑定 this
    this.greet = this.greet.bind(this);
  }

  greet() {
    console.log(`Hello, my name is ${this.name}.`);
  }
}
// const g = new P3("张三");
// g.greet(); // "Hello, my name is 张三."
const g = new P3("张三").greet;
g(); // "Hello, my name is 张三."

function bound(originalMethod: any, context: ClassMethodDecoratorContext) {
  const methodName = context.name;
  if (context.private) {
    throw new Error(`不能绑定私有方法 ${methodName as string}`);
  }
  // this的绑定必须放在构造方法里面，因为这必须在类的初始化阶段完成。它可以移到方法装饰器的addInitializer()里面。
  context.addInitializer(function () {
    // @ts-ignore
    this[methodName] = this[methodName].bind(this);
  });
}
class P4 {
  name: string;
  constructor(name: string) {
    this.name = name;
  }

  @bound
  greet() {
    console.log(`Hello, my name is ${this.name}.`);
  }
}
const g4 = new P4("李四").greet;
g4();

function collect(value: any, context: ClassMethodDecoratorContext) {
  const { name, addInitializer } = context;
  addInitializer(function () {
    // @ts-ignore
    if (!this.collectedMethodKeys) {
      // @ts-ignore
      this.collectedMethodKeys = new Set();
    }
    // @ts-ignore
    this.collectedMethodKeys.add(name);
  });
}
class P5 {
  @collect
  toString() {}

  @collect
  [Symbol.iterator]() {}
}
const inst = new P5();
// @ts-ignore
console.log(inst.collectedMethodKeys);
// ------------------------------------------------------------------
